Skip to content

Test: Add local test schema store - #152

Draft
ClausHolbechArista wants to merge 1 commit into
aristanetworks:mainfrom
ClausHolbechArista:test-schema-store
Draft

Test: Add local test schema store#152
ClausHolbechArista wants to merge 1 commit into
aristanetworks:mainfrom
ClausHolbechArista:test-schema-store

Conversation

@ClausHolbechArista

@ClausHolbechArista ClausHolbechArista commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Add a test schema store in the crate of the same name, which will be used for all tests across both Rust and Python.
This test schema exercises all AVD schema types and fields.
The schema will be uploaded to releases as an artifact, but the main use case is to compile the binary and export it with that.

Next step is to figure out a good way to get this binary into the AVD workflows. Maybe adding it to pyavd-utils python package.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for schema version field in validation schemas.
  • Chores

    • Updated test schema infrastructure to build locally instead of downloading external artifacts.
    • Added three new test schemas (avd_design, eos_config, schema_features) to improve validation testing coverage.
    • Updated third-party license attributions.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR replaces the remote test schema download mechanism with local generation from YAML files, adds three comprehensive test schemas for validation feature coverage, integrates the generated artifact into the CI release workflow, and updates the test suite to use the new schemas.

Changes

Local Test Schema Store Generation

Layer / File(s) Summary
Schema version field support
rust/avdschema/src/schema/dict.rs
Dict struct gains an optional schema_version field deserialized from $version to support schema versioning in YAML definitions.
New test schema definitions
rust/test_schema_store/test_schemas/*.schema.yml, rust/test_schema_store/test_schemas/README.md
Three YAML test schemas added: avd_design (design-like config with fabric_label and structured_config passthrough), eos_config (strict dict with named_items records and config_only_setting), and schema_features (comprehensive feature coverage including defaults, bounds, type conversion, nested refs, and deprecation semantics).
Test schema store library refactor
rust/test_schema_store/src/lib.rs, rust/test_schema_store/Cargo.toml
Replace remote HTTP download with local YAML-based generation: initialize() now scans test_schemas/ directory, derives schema names from filenames, builds an in-memory store, and writes gzipped output; new test_schema_store() and write_store_file() public functions; switch from reqwest to local avdschema path dependency with dump_load_files feature.
Test schema store CLI binary
rust/test_schema_store/src/bin/build_test_schemas.rs
New CLI entrypoint that parses --output (required) and --help arguments, invokes write_store_file() to generate the gzipped schema artifact at the specified path.
Python test fixture
tests/validation/conftest.py
Refactor init_store fixture to check TEST_SCHEMA_STORE_FILE env variable first; if not set, derive repo root, invoke cargo to build local schema store, write to tests/validation/tmp/test-schemas.json.gz, and initialize from that file; remove remote URL download mechanism.
CI workflow integration
.github/workflows/release.yml
Add build-test-schema-store job to generate the artifact via cargo and upload as workflow artifact; add upload-test-schemas job (release-only) to upload to GitHub release assets; update test-pyavd-utils-from-test-pypi and test-pyavd-utils-from-test-pypi-musl jobs to depend on build job, download artifact, and set TEST_SCHEMA_STORE_FILE before running tests.
Validation test suite updates
rust/pyvalidation/src/lib.rs, rust/pyvalidation/benches/performance.rs, tests/validation/test_*.py
Update Rust and Python validation tests to use new schema fields: replace ethernet_interfaces with named_items, replace router_isis with config_only_setting, replace fabric_name with fabric_label; adjust expected violation paths, ignored-key assertions, and benchmark data to match new schema shapes and cross-schema references.
Configuration and metadata
Cargo.toml, pyproject.toml, pyavd_utils/THIRD_PARTY_LICENSES.txt, pyavd_utils/validation.pyi
Remove rust/test_schema_store from workspace exclude; add version bump rules for three schema YAML files; add test_schema_store 0.0.6 to license attribution; remove outdated docstring about schema filename in init_store_from_file.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • aristanetworks/pyavd-utils#142: Adds test_schema_store 0.0.6 to THIRD_PARTY_LICENSES.txt license attribution, overlapping at the same license file level.

Suggested reviewers

  • gmuloc

Poem

🐰 Local schemas stored in YAML, no download required today,
Three test schemas with version fields, validation on display,
Cargo builds the gzipped store, CI artifacts at bay,
Fixtures check the env first, then build their local way! 📦✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description lacks required template sections: no 'Change Summary', 'Related Issue(s)', 'Component(s) name', 'Proposed changes', 'How to test', or 'Checklist' completion. Follow the repository's PR template by adding all required sections including change summary, component(s), proposed changes details, and testing methodology.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding a local test schema store for testing purposes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/test_schema_store/src/bin/build_test_schemas.rs`:
- Line 8: Add a brief comment above the standalone `use avdschema as _;` in
build_test_schemas.rs explaining it is present only to satisfy
`#![deny(unused_crate_dependencies)]` for this binary target (to mark the
dependency as used) and does not imply runtime registration or side‑effects;
reference the fact that the binary delegates to
`test_schema_store::write_store_file` and that `avdschema` is actually used in
the library via `use avdschema::Dump as _;`/`use avdschema::Store;` so readers
understand the lint suppression intent.

In `@rust/test_schema_store/src/lib.rs`:
- Around line 51-65: The filter currently only checks for ".yml" extensions but
schema_name(path) expects filenames ending with ".schema.yml" and will panic on
mismatch; update the directory filter closure (the .filter(...) that uses
extension() and OsStr::new("yml")) to instead check the file name ends_with
".schema.yml" (e.g. use path.file_name().and_then(OsStr::to_str).is_some_and(|f|
f.ends_with(".schema.yml"))), so only conforming files reach schema_name(), or
alternatively make schema_name() return a Result/Option instead of unwrap — pick
the former to keep schema_name() as-is.

In `@tests/validation/conftest.py`:
- Around line 36-51: The subprocess.run invocation that spins up the cargo
command in tests/validation/conftest.py should include a timeout argument to
avoid indefinite hangs; update the subprocess.run call (the one invoking cargo
"run" -p "test_schema_store" --bin "build_test_schemas") to pass a reasonable
timeout (e.g., seconds as an int) and wrap it to catch subprocess.TimeoutExpired
so you can fail the test with a clear message or re-raise a descriptive
exception; ensure you reference the same subprocess.run call and handle
TimeoutExpired to provide a helpful error instead of letting the test suite
hang.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e3033d82-d7d3-4e61-8845-7649a28dd533

📥 Commits

Reviewing files that changed from the base of the PR and between 0264006 and debeff8.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • .github/workflows/release.yml
  • Cargo.toml
  • pyavd_utils/THIRD_PARTY_LICENSES.txt
  • pyavd_utils/validation.pyi
  • pyproject.toml
  • rust/avdschema/src/schema/dict.rs
  • rust/pyvalidation/benches/performance.rs
  • rust/pyvalidation/src/lib.rs
  • rust/test_schema_store/Cargo.toml
  • rust/test_schema_store/src/bin/build_test_schemas.rs
  • rust/test_schema_store/src/lib.rs
  • rust/test_schema_store/test_schemas/README.md
  • rust/test_schema_store/test_schemas/avd_design.schema.yml
  • rust/test_schema_store/test_schemas/eos_config.schema.yml
  • rust/test_schema_store/test_schemas/schema_features.schema.yml
  • tests/validation/conftest.py
  • tests/validation/test_get_validated_data.py
  • tests/validation/test_validate_json.py
💤 Files with no reviewable changes (2)
  • pyavd_utils/validation.pyi
  • Cargo.toml


use std::path::PathBuf;

use avdschema as _;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if removing the avdschema import causes build or runtime issues

# Search for any dynamic registration or lazy_static patterns in avdschema
echo "=== Checking for registration patterns in avdschema ==="
rg -n "inventory::|ctor::|lazy_static!" rust/avdschema/

# Check if binary builds without the import (test compilation only, don't modify)
echo -e "\n=== Checking what symbols the binary imports from avdschema ==="
rg -n "avdschema::" rust/test_schema_store/src/bin/build_test_schemas.rs

Repository: aristanetworks/pyavd-utils

Length of output: 191


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target binary around the import
echo "=== build_test_schemas.rs (context) ==="
sed -n '1,120p' rust/test_schema_store/src/bin/build_test_schemas.rs

# Confirm whether the crate name is imported anywhere else
echo -e "\n=== Other avdschema imports in rust/ ==="
rg -n "use\s+avdschema\s+as\s+_" rust || true
rg -n "extern\s+crate\s+avdschema" rust || true

# Search in the avdschema crate for common side-effect registration entrypoints
echo -e "\n=== Side-effect / registration patterns in rust/avdschema/ ==="
rg -n "inventory::|ctor::|lazy_static!|linkme::|distributed_slice!|register_.*|init_.*|side_effect|#[[]used|used\]" rust/avdschema/ || true

# Check for any re-exported module paths that might be used only for side effects
echo -e "\n=== avdschema:: usage within the binary ==="
rg -n "avdschema::" rust/test_schema_store/src/bin/build_test_schemas.rs || true

# Show the relevant Cargo.toml dependencies
echo -e "\n=== Cargo.toml files referencing avdschema ==="
rg -n "avdschema" rust/test_schema_store/Cargo.toml rust/**/Cargo.toml || true

Repository: aristanetworks/pyavd-utils

Length of output: 2422


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect avdschema crate entrypoints
echo "=== rust/avdschema/src/lib.rs (top) ==="
ls -la rust/avdschema/src || true
if [ -f rust/avdschema/src/lib.rs ]; then
  sed -n '1,200p' rust/avdschema/src/lib.rs
fi

# Look for side-effect / registration related constructs (no broken regex)
echo -e "\n=== Search for registration / ctor / inventory usage in rust/avdschema ==="
rg -n -S -e "inventory::" -e "ctor::" -e "lazy_static!" -e "linkme::" -e "distributed_slice!" \
       -e "register_" -e "init_" -e "side_effect" -e "#\[ctor" -e "#\[distributed_slice" rust/avdschema/ || true

# Search for proc-macro style registration that might rely on crate linking
echo -e "\n=== Search for macros or derive patterns that suggest global registries ==="
rg -n -S -e "distributed_slice!" -e "inventory::collect!" -e "inventory::submit!" -e "linkme::distributed_slice" \
       -e "OnceCell|once_cell" -e "lazy_static" rust/avdschema/ || true

# Confirm the feature enabling dump/load files for this binary
echo -e "\n=== rust/test_schema_store/Cargo.toml dependency on avdschema ==="
sed -n '1,120p' rust/test_schema_store/Cargo.toml

Repository: aristanetworks/pyavd-utils

Length of output: 3235


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate write_store_file implementation and inspect for avdschema usage
echo "=== Find write_store_file in rust/test_schema_store ==="
rg -n "fn\s+write_store_file|write_store_file\(" rust/test_schema_store/src || true

# Show the lib.rs (and any likely modules) around write_store_file
echo -e "\n=== Candidate files for write_store_file ==="
rg -n "pub\s+fn\s+write_store_file|write_store_file" rust/test_schema_store/src -S || true

# Print those files with line numbers (keep output small by limiting ranges)
python3 - <<'PY'
import subprocess, re, os, textwrap, json, sys, pathlib
root="."
# Collect filenames containing "write_store_file"
out=subprocess.check_output(["rg","-l","write_store_file","rust/test_schema_store/src"], text=True).splitlines()
print("\n".join(out))
PY

Repository: aristanetworks/pyavd-utils

Length of output: 619


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== rust/test_schema_store/src/lib.rs (write_store_file area) ==="
sed -n '1,120p' rust/test_schema_store/src/lib.rs

Repository: aristanetworks/pyavd-utils

Length of output: 2067


Document why use avdschema as _; is kept in build_test_schemas (lint suppression)

The binary has #![deny(unused_crate_dependencies)] but doesn’t reference avdschema directly (it only calls test_schema_store::write_store_file); avdschema is used in test_schema_store’s library (use avdschema::Dump as _;, use avdschema::Store;). The use avdschema as _; on line 8 is therefore likely just to mark the dependency as used for this target—so add a short comment to reflect that intent and avoid implying runtime registration/side-effect behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/test_schema_store/src/bin/build_test_schemas.rs` at line 8, Add a brief
comment above the standalone `use avdschema as _;` in build_test_schemas.rs
explaining it is present only to satisfy `#![deny(unused_crate_dependencies)]`
for this binary target (to mark the dependency as used) and does not imply
runtime registration or side‑effects; reference the fact that the binary
delegates to `test_schema_store::write_store_file` and that `avdschema` is
actually used in the library via `use avdschema::Dump as _;`/`use
avdschema::Store;` so readers understand the lint suppression intent.

Comment on lines +51 to 65
.filter(|path| {
path.extension()
.is_some_and(|extension| extension == OsStr::new("yml"))
})
.map(|schema_path| (schema_name(&schema_path), schema_path))
.collect()
}

fn schema_name(path: &Path) -> String {
path.file_name()
.and_then(OsStr::to_str)
.and_then(|filename| filename.strip_suffix(".schema.yml"))
.unwrap()
.to_owned()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Filter and name extraction logic mismatch will panic on non-conforming files.

The directory filter (lines 51-54) accepts any file with a .yml extension, but schema_name() (lines 59-65) requires filenames to end with .schema.yml and panics via unwrap() if they don't. A stray file like notes.yml in test_schemas/ would pass the filter but crash at line 63.

🔧 Fix: Align filter to match expected suffix
 fn read_test_schema_paths() -> HashMap<String, PathBuf> {
     std::fs::read_dir(TEST_SCHEMA_DIR)
         .unwrap()
         .filter_map(Result::ok)
         .map(|entry| entry.path())
         .filter(|path| {
-            path.extension()
-                .is_some_and(|extension| extension == OsStr::new("yml"))
+            path.file_name()
+                .and_then(OsStr::to_str)
+                .is_some_and(|name| name.ends_with(".schema.yml"))
         })
         .map(|schema_path| (schema_name(&schema_path), schema_path))
         .collect()
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.filter(|path| {
path.extension()
.is_some_and(|extension| extension == OsStr::new("yml"))
})
.map(|schema_path| (schema_name(&schema_path), schema_path))
.collect()
}
fn schema_name(path: &Path) -> String {
path.file_name()
.and_then(OsStr::to_str)
.and_then(|filename| filename.strip_suffix(".schema.yml"))
.unwrap()
.to_owned()
}
.filter(|path| {
path.file_name()
.and_then(OsStr::to_str)
.is_some_and(|name| name.ends_with(".schema.yml"))
})
.map(|schema_path| (schema_name(&schema_path), schema_path))
.collect()
}
fn schema_name(path: &Path) -> String {
path.file_name()
.and_then(OsStr::to_str)
.and_then(|filename| filename.strip_suffix(".schema.yml"))
.unwrap()
.to_owned()
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/test_schema_store/src/lib.rs` around lines 51 - 65, The filter currently
only checks for ".yml" extensions but schema_name(path) expects filenames ending
with ".schema.yml" and will panic on mismatch; update the directory filter
closure (the .filter(...) that uses extension() and OsStr::new("yml")) to
instead check the file name ends_with ".schema.yml" (e.g. use
path.file_name().and_then(OsStr::to_str).is_some_and(|f|
f.ends_with(".schema.yml"))), so only conforming files reach schema_name(), or
alternatively make schema_name() return a Result/Option instead of unwrap — pick
the former to keep schema_name() as-is.

Comment on lines +36 to +51
subprocess.run( # noqa: S603
[
cargo,
"run",
"--locked",
"-p",
"test_schema_store",
"--bin",
"build_test_schemas",
"--",
"--output",
str(output_file),
],
check=True,
cwd=repo_root,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add timeout to subprocess.run to prevent indefinite hangs.

The subprocess.run call lacks a timeout parameter. If the cargo build hangs or takes unexpectedly long, the test suite will hang indefinitely, potentially blocking CI pipelines.

⏱️ Proposed fix to add timeout
     subprocess.run(  # noqa: S603
         [
             cargo,
             "run",
             "--locked",
             "-p",
             "test_schema_store",
             "--bin",
             "build_test_schemas",
             "--",
             "--output",
             str(output_file),
         ],
         check=True,
         cwd=repo_root,
+        timeout=300,  # 5 minutes should be sufficient for building the schema store
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/validation/conftest.py` around lines 36 - 51, The subprocess.run
invocation that spins up the cargo command in tests/validation/conftest.py
should include a timeout argument to avoid indefinite hangs; update the
subprocess.run call (the one invoking cargo "run" -p "test_schema_store" --bin
"build_test_schemas") to pass a reasonable timeout (e.g., seconds as an int) and
wrap it to catch subprocess.TimeoutExpired so you can fail the test with a clear
message or re-raise a descriptive exception; ensure you reference the same
subprocess.run call and handle TimeoutExpired to provide a helpful error instead
of letting the test suite hang.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@gmuloc
gmuloc marked this pull request as draft June 17, 2026 07:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant